package org.bhf.json;

/**
 * Copyright (c) 2007, Blue Hole Software. All rights reserved.
 * Code licensed under the Apache 2.0 License:
 * http://www.apache.org/licenses/
 */

import java.lang.reflect.Method;
import java.lang.reflect.Type;
import java.util.Date;

/**
 * Collection of built-in parsers
 */
public final class NativeJSONParsers
{
    /**
     * Artificially public: do not call. Parse a JSON value
     *
     * @param object for overlay. May be <code>null</code>
     * @param tokens Parser
     * @param parser Back reference to the current JSONParser
     * @return null if value is "null"; otherwise, parsed value
     * @exclude
     */
    public static Boolean parse( final Boolean object, final JSONTokenizer tokens, final JSONParser parser )
    {
        final String value = tokens.nextAnyValueString();

        if( value == null )
        {
            parser.messages.add( new ConversionError( parser.context, value, Boolean.class ) );
            return object;
        }

        // For form submits
        if( "on".equalsIgnoreCase( value ) || "checked".equalsIgnoreCase( value ) )
            return true;

        return Boolean.parseBoolean( value );
    }

    /**
     * Artificially public: do not call. Parse a JSON value
     *
     * @param object for overlay. May be <code>null</code>
     * @param tokens Parser
     * @param parser Back reference to the current JSONParser
     * @return null if value is "null"; otherwise, parsed value
     * @throws ValueFormatException Unable to parse value into desired type
     * @exclude
     */
    public static boolean parse( final boolean object, final JSONTokenizer tokens, final JSONParser parser )
        throws ValueFormatException
    {
        return parse( (Boolean)object, tokens, parser );
    }

    /**
     * Artificially public: do not call. Parse a JSON value
     *
     * @param object for overlay. May be <code>null</code>
     * @param tokens Parser
     * @param parser Back reference to the current JSONParser
     * @return null if value is "null"; otherwise, parsed value
     * @throws ValueFormatException Unable to parse value into desired type
     * @exclude
     */
    public static Character parse( final Character object, final JSONTokenizer tokens, final JSONParser parser )
        throws ValueFormatException
    {
        final String value = tokens.nextAnyValueString();
        if( value == null ) return null;
        if( value.length() != 1 )
        {
            parser.messages.add( new ConversionError( parser.context, value, Character.class ) );
            return object;
        }

        return value.charAt(0);
    }

    /**
     * Artificially public: do not call. Parse a JSON value
     *
     * @param object for overlay. May be <code>null</code>
     * @param tokens Parser
     * @param parser Back reference to the current JSONParser
     * @return null if value is "null"; otherwise, parsed value
     * @throws ValueFormatException Unable to parse value into desired type
     * @exclude
     */
    public static char parse( final char object, final JSONTokenizer tokens, final JSONParser parser )
        throws ValueFormatException
    {
        return parse( (Character)object, tokens, parser );
    }

    /**
     * Artificially public: do not call. Parse a JSON value
     *
     * @param object for overlay. May be <code>null</code>
     * @param tokens Parser
     * @param parser Back reference to the current JSONParser
     * @return null if value is "null"; otherwise, parsed value
     * @throws ValueFormatException Unable to parse value into desired type
     * @exclude
     */
    public static Byte parse( final Byte object, final JSONTokenizer tokens, final JSONParser parser )
        throws ValueFormatException
    {
        final String value = tokens.nextAnyValueString();
        if( value == null ) return null;
        try
        {
            return Byte.valueOf( value );
        }
        catch( NumberFormatException nfe )
        {
            parser.messages.add( new ConversionError( parser.context, value, Byte.class ) );
            return object;
        }
    }

    /**
     * Artificially public: do not call. Parse a JSON value
     *
     * @param object for overlay. May be <code>null</code>
     * @param tokens Parser
     * @param parser Back reference to the current JSONParser
     * @return null if value is "null"; otherwise, parsed value
     * @throws ValueFormatException Unable to parse value into desired type
     * @exclude
     */
    public static byte parse( final byte object, final JSONTokenizer tokens, final JSONParser parser )
        throws ValueFormatException
    {
        return parse( (Byte)object, tokens, parser );
    }

    /**
     * Artificially public: do not call. Parse a JSON value
     *
     * @param object for overlay. May be <code>null</code>
     * @param tokens Parser
     * @param parser Back reference to the current JSONParser
     * @return null if value is "null"; otherwise, parsed value
     * @throws ValueFormatException Unable to parse value into desired type
     * @exclude
     */
    public static Short parse( final Short object, final JSONTokenizer tokens, final JSONParser parser )
        throws ValueFormatException
    {
        final String value = tokens.nextAnyValueString();
        if( value == null ) return null;
        try
        {
            return Short.valueOf( value );
        }
        catch( NumberFormatException nfe )
        {
            parser.messages.add( new ConversionError( parser.context, value, Short.class ) );
            return object;
        }
    }

    /**
     * Artificially public: do not call. Parse a JSON value
     *
     * @param object for overlay. May be <code>null</code>
     * @param tokens Parser
     * @param parser Back reference to the current JSONParser
     * @return null if value is "null"; otherwise, parsed value
     * @throws ValueFormatException Unable to parse value into desired type
     * @exclude
     */
    public static short parse( final short object, final JSONTokenizer tokens, final JSONParser parser )
        throws ValueFormatException
    {
        return parse( (Short)object, tokens, parser );
    }

    /**
     * Artificially public: do not call. Parse a JSON value
     *
     * @param object for overlay. May be <code>null</code>
     * @param tokens Parser
     * @param parser Back reference to the current JSONParser
     * @return null if value is "null"; otherwise, parsed value
     * @throws ValueFormatException Unable to parse value into desired type
     * @exclude
     */
    public static Integer parse( final Integer object, final JSONTokenizer tokens, final JSONParser parser )
        throws ValueFormatException
    {
        final String value = tokens.nextAnyValueString();
        if( value == null ) return null;
        try
        {
            return Integer.valueOf( value );
        }
        catch( NumberFormatException nfe )
        {
            parser.messages.add( new ConversionError( parser.context, value, Integer.class ) );
            return object;
        }
    }

    /**
     * Artificially public: do not call. Parse a JSON value
     *
     * @param object for overlay. May be <code>null</code>
     * @param tokens Parser
     * @param parser Back reference to the current JSONParser
     * @return null if value is "null"; otherwise, parsed value
     * @throws ValueFormatException Unable to parse value into desired type
     * @exclude
     */
    public static int parse( final int object, final JSONTokenizer tokens, final JSONParser parser )
        throws ValueFormatException
    {
        return parse( (Integer)object, tokens, parser );
    }

    /**
     * Artificially public: do not call. Parse a JSON value
     *
     * @param object for overlay. May be <code>null</code>
     * @param tokens Parser
     * @param parser Back reference to the current JSONParser
     * @return null if value is "null"; otherwise, parsed value
     * @throws ValueFormatException Unable to parse value into desired type
     * @exclude
     */
    public static Long parse( final Long object, final JSONTokenizer tokens, final JSONParser parser )
        throws ValueFormatException
    {
        final String value = tokens.nextAnyValueString();
        if( value == null ) return null;
        try
        {
            return Long.valueOf( value );
        }
        catch( NumberFormatException nfe )
        {
            parser.messages.add( new ConversionError( parser.context, value, Long.class ) );
            return object;
        }
    }

    /**
     * Artificially public: do not call. Parse a JSON value
     *
     * @param object for overlay. May be <code>null</code>
     * @param tokens Parser
     * @param parser Back reference to the current JSONParser
     * @return null if value is "null"; otherwise, parsed value
     * @throws ValueFormatException Unable to parse value into desired type
     * @exclude
     */
    public static long parse( final long object, JSONTokenizer tokens, final JSONParser parser )
        throws ValueFormatException
    {
        return parse( (Long)object, tokens, parser );
    }

    /**
     * Artificially public: do not call. Parse a JSON value
     *
     * @param object for overlay. May be <code>null</code>
     * @param tokens Parser
     * @param parser Back reference to the current JSONParser
     * @return null if value is "null"; otherwise, parsed value
     * @throws ValueFormatException Unable to parse value into desired type
     * @exclude
     */
    public static Float parse( final Float object, final JSONTokenizer tokens, final JSONParser parser )
        throws ValueFormatException
    {
        final String value = tokens.nextAnyValueString();
        if( value == null ) return null;
        try
        {
            return Float.valueOf( value );
        }
        catch( NumberFormatException nfe )
        {
            parser.messages.add( new ConversionError( parser.context, value, Float.class ) );
            return object;
        }
    }

    /**
     * Artificially public: do not call. Parse a JSON value
     *
     * @param object for overlay. May be <code>null</code>
     * @param tokens Parser
     * @param parser Back reference to the current JSONParser
     * @return null if value is "null"; otherwise, parsed value
     * @throws ValueFormatException Unable to parse value into desired type
     * @exclude
     */
    public static float parse( final float object, final JSONTokenizer tokens, final JSONParser parser )
        throws ValueFormatException
    {
        return parse( (Float)object, tokens, parser );
    }

    /**
     * Artificially public: do not call. Parse a JSON value
     *
     * @param object for overlay. May be <code>null</code>
     * @param tokens Parser
     * @param parser Back reference to the current JSONParser
     * @return null if value is "null"; otherwise, parsed value
     * @throws ValueFormatException Unable to parse value into desired type
     * @exclude
     */
    public static Double parse( final Double object, final JSONTokenizer tokens, final JSONParser parser )
        throws ValueFormatException
    {
        final String value = tokens.nextAnyValueString();
        if( value == null ) return null;
        try
        {
            return Double.valueOf( value );
        }
        catch( NumberFormatException nfe )
        {
            parser.messages.add( new ConversionError( parser.context, value, Double.class ) );
            return object;
        }
    }

    /**
     * Artificially public: do not call. Parse a JSON value
     *
     * @param object for overlay. May be <code>null</code>
     * @param tokens Parser
     * @param parser Back reference to the current JSONParser
     * @return null if value is "null"; otherwise, parsed value
     * @throws ValueFormatException Unable to parse value into desired type
     * @exclude
     */
    public static double parse( final double object, final JSONTokenizer tokens, final JSONParser parser )
        throws ValueFormatException
    {
        return parse( (Double)object, tokens, parser );
    }

    //
    // Non-primitive Built-in Objects
    //

    /**
     * Artificially public: do not call. Parse a JSON value
     *
     * @param object for overlay. May be <code>null</code>
     * @param tokens Parser
     * @param parser Back reference to the current JSONParser
     * @return null if value is "null"; otherwise, parsed value
     * @throws ValueFormatException Unable to parse value into desired type
     * @exclude
     */
    public static String parse( final String object, final JSONTokenizer tokens, final JSONParser parser )
        throws ValueFormatException
    {
        final String value = tokens.nextAnyValueString();
        if( value == null ) return null;
        return value;
    }

    /**
     * Artificially public: do not call. Parse a JSON value
     *
     * @param date for overlay. May be <code>null</code>
     * @param tokens Parser
     * @param parser Back reference to the current JSONParser
     * @return null if value is "null"; otherwise, parsed value
     * @throws ValueFormatException Unable to parse value into desired type
     * @exclude
     */
    public static Date parse( final Date date, final JSONTokenizer tokens, final JSONParser parser )
        throws ValueFormatException
    {
        String value = tokens.nextAnyValueString();
        if( value == null ) return null;

        try
        {
            if( value.length() > 2 && value.charAt(0) == '@' && value.charAt(value.length()-1) == '@' )
                value = value.substring( 1, value.length() - 1 );

            final long ms = Long.valueOf( value );
            if( date != null )
            {
                date.setTime( ms );
                return date;
            }
            else
            {
                return new Date( ms );
            }
        }
        catch( NumberFormatException nfe )
        {
            parser.messages.add( new ConversionError( parser.context, value, Long.class ) );
            return date;
        }
    }

    //
    // Package
    //

    /**
     * Return <code>true</code> if we have built-in support for the given <code>Type</code>. In other
     * words, for built-in types we do not have to generate a custom writer and can use the write
     * methods directly.
     *
     * @param type Must not be <code>null</code>
     * @return <code>true</code> if we have built-in support for the given <code>Type</code>.
     */
    static boolean  hasBuiltInSupport( final Type type )
    {
        return getBuiltInMethod( type ) != null;
    }

    /**
     * Return <code>true</code> if we have built-in support for the given <code>Type</code>. In other
     * words, for built-in types we do not have to generate a custom writer and can use the write
     * methods directly.
     *
     * @param type Must not be null
     * @return Reader method or null.
     */
    static Method   getBuiltInMethod( final Type type )
    {
        return getParseMethod( NativeJSONParsers.class, type );
    }

    //
    // Private
    //

    private static Method   getParseMethod( final Class parserClass, final Type type )
    {
        try
        {
            final Class clazz = Util.digRawType( type );
            return parserClass.getMethod( "parse", clazz, JSONTokenizer.class, JSONParser.class );
        }
        catch( NoSuchMethodException e )
        {
            return null;
        }
        catch( UnsupportedTypeException e )
        {
            return null;
        }
    }
}